{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Multiple dispatch"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Multiple dispatch** is a key feature of Julia, that we will explore in this notebook.\n",
    "\n",
    "It helps make software fast. It also makes software extensible, programmable, and downright fun to play with. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1. Roman numerals\n",
    "2. Functions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Roman numerals (for fun)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's define a **new struct** that represents a Roman numeral. For coding simplicity, we'll just deal with numbers between 0 and 9. \n",
    "\n",
    "**Exercise**: Extend this to larger numbers. (Recall that Roman numbers are a base-10 system!)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "struct Roman\n",
    "    n::Int\n",
    "end\n",
    "\n",
    "Base.show(io::IO, r::Roman) = print(io, 'ⅰ' + (r.n - 1) % 10 )  # nice display; 'ⅰ' is a Unicode Roman numeral"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can create an object of this type as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Roman(4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "typeof.([5 5.0 Roman(5) \"Five\" '5'  5//1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We would like to display it nicely, in Roman numerals:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x = [7 1 2 5 8 9]\n",
    "Roman.(x)   # equivalent to map(Roman, x)  or  [Roman(w) for w in x]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It'd be nice to be able to add Roman numerals together like normal numbers:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Roman(4) + Roman(5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But Julia doesn't know how to do that. Let's teach it by `import`ing the `+` function, which then allows us to _extend_ its definition:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import Base: +, *\n",
    "\n",
    "+(a::Roman, b::Roman) = Roman(a.n + b.n)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Roman(4) + Roman(5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This **adds a new method** to the function `+`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "methods(+)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import Base.*\n",
    "*(i::Roman, j::Roman) = Roman(i.n * j.n)                     # Multiply like a Roman"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Roman(3) * Roman(2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Roman.(1:3) .* [Roman(1) Roman(2) Roman(3)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Roman(3) * 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Complicated mytimes to decide what to do based on type\n",
    "# not suggested, better way coming soon\n",
    "function mytimes(i,j)\n",
    "  if isa(i,Roman) & isa(j,Number)\n",
    "        return  fill(1, i.n, j)   # i by j matrix with ones\n",
    "    elseif    isa(i,Number) & isa(j,Roman) \n",
    "        return \"😄\"^ (i*j.n)   #  i * j happy faces\n",
    "    else\n",
    "        return(\"I Don't know\")\n",
    "    end\n",
    "end"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mytimes(4,Roman(3)) # Twelve happys"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mytimes(Roman(4),3) # 4x3 matrix with ones"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The simplest thing to do is to explicitly define multiplication of a `Roman` by a number. We can do it as we see fit:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "*(i::Number, j::Roman) = \"😄\"^ (i*j.n)        #  i * j happy faces\n",
    "\n",
    "*(i::Roman, j::Number) =   fill(1, i.n, j)       # i by j matrix"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "3 * Roman(3) # Nine happys"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Roman(3) * 5  # Three by Five matrix of ones"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "t(x::Roman,y::Roman) = x.n * y.n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "t(Roman(5),Roman(4))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#  Notice how tight the assembler is!\n",
    "@code_native t(Roman(2),Roman(4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Functions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import Base: *, +, ^"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "*(α::Number,   g::Function) = x -> α * g(x)   # Scalar times function\n",
    "\n",
    "*(f::Function, λ::Number)   = x -> f(λ * x)   # Scale the argument\n",
    "\n",
    "*(f::Function, g::Function) = x -> f(g(x))    # Function composition  -- abuse of notation!  use \\circ in Julia 0.6\n",
    "\n",
    "^(f::Function, n::Integer) = n == 1 ? f : f*f^(n-1) # A naive exponentiation algorithm by recursive multiplication"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "+(f::Function, g::Function) = x -> f(x) + g(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For example, the exponential function is defined as\n",
    "\n",
    "$$\\exp(x) = \\sum_{n=0}^\\infty \\frac{1}{n!} x^n.$$\n",
    "\n",
    "We can think of this just in terms of functions:\n",
    "\n",
    "$$\\exp = \\sum_{n=0}^\\infty \\frac{1}{n!} \\mathrm{pow}_n,$$\n",
    "\n",
    "where $\\mathrm{pow}_n(x) = x^n$.\n",
    "\n",
    "(starts to blur the symbolic with the numerical!)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pow(n) = x -> x^n\n",
    "\n",
    "myexp = sum(1/factorial(big(n)) * pow(n) for n in 0:100)   # taylor series not efficient!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "[myexp(1); exp(1); exp(big(1))]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f = x -> x^2\n",
    "f(10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "g = 3f\n",
    "g(10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "(f^2)(10)  # since we defined multiplication of functions as composition"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "using Plots;\n",
    "gr()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x = pi*(0:0.001:4)\n",
    "\n",
    "plot(x, sin.(x),    c=\"black\", label=\"Fun\")\n",
    "plot!(x, (12*sin).(x),    c=\"green\", label=\"Num * Fun\")\n",
    "plot!(x, (sin*12).(x),    c=\"red\", alpha=0.9, label=\"Fun * Num\")\n",
    "plot!(x, (5*sin*exp).(x), c=\"blue\", alpha=0.2, label=\"Num * Fun * Fun\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot([12*sin, sin*12, 5*sin*exp], 0:.01:4π, α=[1 .9 .2], c=[:green :red :blue])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"https://lh4.googleusercontent.com/--z5eKJbB7sg/UffjL1iAd4I/AAAAAAAABOc/S_wDVyDOBfQ/gauss.jpg\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  \"Sin^2 phi is odious to me, even though Laplace made use of it; should  it be feared that sin^2 phi might become ambiguous, which would perhaps  never occur, or at most very rarely when speaking of sin(phi^2), well  then, let us write (sin phi)^2, but not sin^2 phi, which by analogy  should signify sin(sin phi).\" -- Gauss"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x=(0:.01:2) * pi;\n",
    "\n",
    "plot(x, (sin^2).(x), c=\"blue\")     # Squaring just works, y=sin(sin(x)), Gauss would be pleased!\n",
    "plot!(x, sin.(x).^2,  c=\"red\")         "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Exercise"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "h(a, b::Any) = \"fallback\"\n",
    "h(a::Number, b::Number) = \"a and b are both numbers\"\n",
    "h(a::Number, b) = \"a is a number\"\n",
    "h(a, b::Number) = \"b is a number\"\n",
    "h(a::Integer, b::Integer) = \"a and b are both integers\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Try playing with h"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Julia 1.0.0",
   "language": "julia",
   "name": "julia-1.0"
  },
  "language": "Julia",
  "language_info": {
   "file_extension": ".jl",
   "mimetype": "application/julia",
   "name": "julia",
   "version": "1.0.0"
  },
  "toc": {
   "nav_menu": {
    "height": "119px",
    "width": "251px"
   },
   "navigate_menu": true,
   "number_sections": true,
   "sideBar": true,
   "threshold": "2",
   "toc_cell": false,
   "toc_section_display": "block",
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
